<!--
 * @moduleName: 下拉树组件
 * @Author: dawdler
 * @Date: 2018-12-19 14:03:03
 * @LastModifiedBy: dawdler
 * @LastEditTime: 2019-04-01 15:58:58
 * @ModifiedBy: YJ 20190524 15:08
 -->
<template>
  <div class="uni-tree-select">
    <!-- 下拉文本 -->
    <el-select :style="styles" class="uni-tree-select-input" v-model="labels" :disabled="disabled" popper-class="select-option"
      ref="select" v-bind="propsSelectParams" :popper-append-to-body="false" :filterable="false" v-popover:popover
      @remove-tag="_selectRemoveTag" @clear="_selectClearFun" @focus="_popoverShowFun"></el-select>
    <!-- 弹出框 -->
    <el-popover :disabled="disabled" ref="popover" :placement="placement" popper-class="uni-tree-select-popper" :width="width"
      v-model="visible" trigger="click">
      <!-- 是否显示搜索框 -->
      <el-input v-if="propsTreeParams.filterable" v-model="keywords" size="mini" class="input-with-select mb10" @change="_searchFun"
        clearable>
        <el-button slot="append" icon="el-icon-search" @click="_searchFun"></el-button>
      </el-input>
      <el-scrollbar tag="div" wrap-class="el-select-dropdown__wrap" view-class="el-select-dropdown__list" class="is-empty">
        <!-- 树列表 -->
        <el-tree ref="tree" v-show="treeData.length>0" v-bind="propsTreeParams" :data="treeData" :node-key="propsValue"
          :draggable="false" :current-node-key="ids.length>0?ids[0]:''" :show-checkbox="propsSelectParams.multiple"
          :filter-node-method="_filterFun" @node-click="_treeNodeClickFun" @check="_treeCheckFun"></el-tree>
        <!-- 暂无数据 -->
        <div v-if="treeData.length===0" class="no-data">no data</div>
      </el-scrollbar>
    </el-popover>
  </div>
</template>
<style>
.uni-tree-select .select-option {
  display: none !important;
}

.uni-tree-select-popper {
  max-height: 400px;
  overflow: auto;
}

.uni-tree-select-popper .el-button--small {
  width: 25px !important;
  min-width: 25px !important;
}

.uni-tree-select-popper[x-placement^='bottom'] {
  margin-top: 5px;
}

.mb10 {
  margin-bottom: 10px;
}

.no-data {
  height: 32px;
  line-height: 32px;
  font-size: 14px;
  color: #cccccc;
  text-align: center;
}
</style>
<script>
import { on, off } from '@/utils/dom'
import { each } from '@/utils/utils'
import { setTimeout } from 'timers'

const _selectParams = {
  multiple: false,
  clearable: true,
  disabled: false,
  placeholder: '请选择'
}
const _treeParams = {
  // 是否允许点击父节点
  clickParent: true,
  // 是否显示过滤
  filterable: false,
  // 是否展开所有
  defaultExpandAll: true,
  props: {
    children: 'children',
    label: 'name',
    value: 'id',
    disabled: 'disabled'
  }
}
// @group api
export default {
  name: 'uni-tree-select',
  props: {
    // v-model,存储的是treeParams.data里面的id
    value: {
      // `String` / `Array`
      type: [String, Number, Array],
      default () {
        return ''
      }
    },
    // el-tree data
    data: {
      type: Array,
      default () {
        return []
      }
    },
    // el-tree url
    treeUrl: {
      type: String,
      default () {
        return ''
      }
    },
    // el-tree url para
    treeUrlParams: {
      type: Object,
      default () {
        return {}
      }
    },
    // el-select样式
    styles: {
      type: Object,
      default () {
        return { width: '100%' }
      }
    },
    // 是否禁用文本框
    disabled: {
      type: Boolean,
      default () {
        return false
      }
    },
    // pop的时候是否刷新数据
    refreshData: {
      type: Boolean,
      default () {
        return false
      }
    },
    // 弹出框位置
    placement: {
      type: String,
      default () {
        return 'bottom'
      }
    },
    /*
    文本框参数，几乎支持el-select所有的API<br>
    取消参数：<br>
    设定下拉框的弹出框隐藏：<br>
    `:popper-append-to-body="false"` <br>
    搜索从弹出框里面执行： <br>
    `:filterable="false"`
    */
    selectParams: {
      type: Object,
      /*
      Object默认参数：<br><br>
      是否多选：<br>
      `multiple: true`<br><br>
      是否可以清空选项：<br>
      `clearable: true,`<br><br>
      是否禁用：<br>
      `disabled: false,`<br><br>
      搜索框placeholder文字：<br>
      `placeholder: '请选择',`<br><br>
      */
      default () {
        return { ..._selectParams }
      }
    },
    /*
    下拉树参数，几乎支持el-tree所有的API<br>
     取消参数:<br>
    `:show-checkbox="selectParams.multiple"`<br>
    使用下拉框参数multiple判断是否对树进行多选<br>
    取消对el-tree的人为传参show-checkbox<br>
    `:node-key="propsValue"`     自动获取treeParams.props.value<br>
    `:draggable="false"`         屏蔽拖动
    */
    treeParams: {
      type: Object,
      /*
      Object默认参数：<br><br>
      在有子级的情况下是否点击父级关闭弹出框,false 只能点击子级关闭弹出框：`clickParent: false`<br><br>
      是否显示搜索框：<br>
      `filterable: false`<br><br>
      下拉树的数据：<br>
      `data:[]`<br><br>
      下拉树的props：<br>
      `props: {`<br>
          `children: 'children',`<br>
          `label: 'name',`<br>
          `value: 'flowId',`<br>
          `disabled: 'disabled'`<br>
      `}`
      */
      default () {
        return { ..._treeParams }
      }
    },
    /**
     * 多选模式下、是否只用叶子节点数据
     */
    onlyLeaf: {
      type: Boolean,
      default: () => false
    }
  },
  data () {
    return {
      isUpdateTreeByIds: true,
      propsValue: 'id',
      propsLabel: 'name',
      propsDisabled: 'disabled',
      propsChildren: 'children',
      propsSelectParams: _selectParams,
      propsTreeParams: _treeParams,
      keywords: '',
      labels: '', // 存储名称，用于下拉框显示内容
      ids: [], // 存储id
      selectNodes: [], // 选中数据
      visible: false, // popover v-model
      width: 150,
      treeData: []
    }
  },
  watch: {
    ids: function (val) {
      this.$nextTick(() => {
        // 根据刷新树标记刷新树
        if (this.isUpdateTreeByIds) {
          this._setSelectNodeFun(val)
        } else {
          // 重置更新标识
          this.isUpdateTreeByIds = true
        }
        // 刷新显示值
        this._refreshLabels()
      })
    },
    value: function (val) {
      if (this.ids !== val) {
        this._updateIds(val)
      }
    },
    data: function (val) {
      this.treeData = val
    },
    treeUrl: function (val) {
      // 更新 treeData
      this._treeDataFromUrl(val)
    },
    treeData: function (val) {
      // 数据更新后，刷新树
      this.$nextTick(() => {
        this._setSelectNodeFun(this.ids)
        this._refreshLabels()
      })
    },
    visible: function (val) {
      if (val && this.refreshData) {
        this._loadTreeData()
      }
    }
  },
  created () {
    // 整合集合props
    this.propsSelectParams = {
      ..._selectParams,
      ...this.selectParams
    }
    this.propsTreeParams = {
      ..._treeParams,
      ...this.treeParams
    }
    const { props } = this.propsTreeParams
    this.propsValue = props.value
    this.propsLabel = props.label
    this.propsDisabled = props.disabled
    this.propsChildren = props.children

    this._loadTreeData().then(() => {
      this._updateIds(this.value)
      this.$nextTick(() => {
        this._setSelectNodeFun(this.ids)
        this._refreshLabels()
      })
    })
  },
  mounted () {
    this._updateH()
    this.$nextTick(() => {
      on(document, 'mouseup', this._popoverHideFun)
    })
  },
  methods: {
    /**
     * 更新ids
     */
    _updateIds (val) {
      if (val) {
        this.ids = val instanceof Array ? val : [val]
      } else {
        this.ids = []
      }
    },
    /**
     * 加载树需要的数据
     */
    _loadTreeData () {
      return new Promise((resolve, reject) => {
        // 优先url提供的数据
        if (this.treeUrl) {
          this._treeDataFromUrl(this.treeUrl).then(() => {
            resolve()
          })
        } else {
          this.treeData = this.data
          resolve()
        }
      })
    },
    /**
     * 支持从URL中获取数据
     */
    _treeDataFromUrl (val) {
      return new Promise((resolve, reject) => {
        if (val) {
          this.$http.get(val, { params: this.treeUrlParams }).then(({ data: res }) => {
            if (res.code !== 0) {
              throw new Error(res.msg)
            }
            this.treeData = this.$utils.treeDataTranslate(res.data)
            resolve()
          }).catch(() => { })
        }
      })
    },
    // 输入文本框输入内容抛出
    _searchFun () {
      /*
      对外抛出搜索方法，自行判断是走后台查询，还是前端过滤<br>
      前端过滤：this.$refs.treeSelect.$refs.tree.filter(value)<br>
      后台查询：this.$refs.treeSelect.treeDataUpdateFun(data)
      */
      if (!this._events.searchFun) {
        // 走默认的前端搜索
        this.filterFun(this.keywords)
      } else {
        this.$emit('searchFun', this.keywords)
      }
    },
    // 根据id筛选当前树名称，以及选中树列表
    _setSelectNodeFun (ids) {
      const el = this.$refs.tree
      if (!el) {
        throw new Error(`can't find tree dom`)
      }
      const { multiple } = this.propsSelectParams
      // 长度为0，清空选择
      if (ids.length === 0 || this.treeData.length === 0) {
        this.labels = multiple ? [] : ''
        if (multiple) {
          el.setCheckedKeys([])
        } else {
          el.setCurrentKey('')
        }
        return
      }
      if (multiple) {
        el.setCheckedKeys(ids)
      } else {
        el.setCurrentKey(ids[0])
      }
      this._updatePopoverLocationFun()
    },
    // 处理显示的值
    _refreshLabels () {
      const { multiple } = this.propsSelectParams
      const el = this.$refs.tree
      let ids = this.ids
      if (!el) {
        throw new Error(`can't find tree dom`)
      }
      if (multiple) {
        this.labels = el.getCheckedNodes().filter(item => {
          if (this.onlyLeaf) {
            if (item[this.propsChildren] && item[this.propsChildren].length > 0) { } else {
              return item
            }
          } else {
            return item
          }
        }).map(item => item[this.propsLabel]) || []
      } else {
        // 2019.12.20 设置数据集不存在的值 仍能取到getCurrentNode 增加值判断
        if (el.getCurrentNode() && el.getCurrentNode()[this.propsValue] === ids[0]) {
          this.labels = el.getCurrentNode()[this.propsLabel]
        } else {
          this.labels = ''
        }
      }
    },
    // 更新popover位置
    _updatePopoverLocationFun () {
      // dom高度还没有更新，做一个延迟
      setTimeout(() => {
        this.$refs.popover.updatePopper()
      }, 50)
    },
    // 树过滤
    _filterFun (value, data, node) {
      if (!value) return true
      return data[this.propsLabel].indexOf(value) !== -1
    },
    // 树点击
    _treeNodeClickFun (data, node, vm) {
      const { multiple } = this.propsSelectParams
      const { clickParent } = this.propsTreeParams
      const { propsValue, propsChildren, propsDisabled } = this
      const { checkOnClickNode, checkStrictly } = this.$refs.tree
      if (data[propsDisabled]) {
        // 禁用
        return false
      }

      if (multiple) {
        // 多选
        // 如果配置了点击节点自动check则跳过
        if (!checkOnClickNode) {
          this.$refs.tree.setChecked(data, !node.checked, !checkStrictly)
          this._treeCheckFun(data, node, vm)
        }
        // value赋值转到 _treeCheckFun 方法中
      } else {
        // 单选
        if (!clickParent) {
          const children = data[propsChildren]
          // 如果不允许点击父级,自身为末级，允许点击之后关闭
          if (!children || children.length === 0) {
            this.ids = [data[propsValue]]
            this.visible = false
          } else {
            // 不允许父级，阻止继续派发
            return false
          }
        } else {
          this.ids = [data[propsValue]]
          this.visible = false
        }
        this.isUpdateTreeByIds = false
        this._emitFun()
      }
      /*
      点击节点，对外抛出   `data, node, vm`<br>
      `data:` 当前点击的节点数据<br>
      `node:` 当前点击的node<br>
      `vm:` 当前组件的vm
      */
      this.$emit('node-click', data, node, vm)
    },
    // 树勾选
    _treeCheckFun (data, node, vm) {
      this.ids = []
      const { propsValue } = this
      this.$refs.tree.getCheckedNodes().forEach(item => {
        if (this.onlyLeaf) {
          if (item[this.propsChildren] && item[this.propsChildren].length > 0) { } else {
            this.ids.push(item[propsValue])
          }
        } else {
          this.ids.push(item[propsValue])
        }
      })
      // 不更新树
      this.isUpdateTreeByIds = false
      /*
      点击复选框，对外抛出   `data, node, vm`<br>
      `data:` 当前点击的节点数据<br>
      `node:` 当前点击的node<br>
      `vm:` 当前组件的vm
      */
      this.$emit('check', data, node, vm)
      this._emitFun()
    },
    // 下拉框移除tag时触发
    _selectRemoveTag (tag) {
      const { treeData, propsValue, propsLabel, propsChildren } = this
      const data = treeData
      each(
        data,
        item => {
          if (item[propsLabel] === tag) {
            const value = item[propsValue]
            this.ids = this.ids.filter(id => id !== value)
          }
        },
        propsChildren
      )
      this._emitFun()
    },
    // 下拉框清空数据
    _selectClearFun () {
      this.ids = []
      const { multiple } = this.propsSelectParams
      // 下拉框清空，对外抛出``this.$emit('input', multiple ? [] : '')`
      this.$emit('input', multiple ? [] : '')
      // 下拉框清空，对外抛出``this.$emit('select-clear')`
      this.$emit('select-clear')
      this._updatePopoverLocationFun()
    },
    // 判断类型，抛出当前选中id
    _emitFun () {
      const { multiple } = this.propsSelectParams
      this.$emit('input', multiple ? this.ids : this.ids.length > 0 ? this.ids[0] : '')
      this._updatePopoverLocationFun()
    },
    // 更新宽度
    _updateH () {
      this.$nextTick(() => {
        this.width = this.$refs.select.$el.getBoundingClientRect().width
      })
    },
    // 显示弹出框的时候容错，查看是否和el宽度一致
    _popoverShowFun () {
      this._updateH()
    },
    // 判断是否隐藏弹出框
    _popoverHideFun (e) {
      let isInter
      if (e && e.path) {
        isInter = e.path.some(list => {
          return list.className && list.className.indexOf('uni-tree-select') !== -1
        })
      }
      if (!isInter) {
        this.visible = false
      }
    },
    /**
     * @vuese
     * 树列表更新数据
     * @arg Array
     */
    treeDataUpdateFun (data) {
      this.treeData = data
      // // 数据更新后，刷新select显示 等待DOM做延时
      // setTimeout(() => {
      //   this._setSelectNodeFun(this.ids)
      // }, 100)
    },
    /**
     * @vuese
     * 本地过滤方法
     * @arg String
     */
    filterFun (val) {
      this.$refs.tree.filter(val)
    }
  },
  components: {},
  beforeDestroy () {
    off(document, 'mouseup', this._popoverHideFun)
  }
}
</script>
